package com.firebugsoft.generator.dubbo.provider.component;

import com.alibaba.dubbo.common.json.JSON;
import com.alibaba.dubbo.common.json.ParseException;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

/**
 * twitter snow flake generator
 * 从左向右
 * 63位: 置0
 * 62-22位:timestamp 时间戳
 * 22-12位:workid 工作机器id
 * 11-0位:sequence 序列号
 * 使用注意：
 * 本机时间不能调整
 */
@Component
public class SnowflakeComponent {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    @Value("${zk.addr}")
    private String addr;
    @Value("${zk.snowflake.path.workers}")
    private String path;
    //
    private long timestamp = System.currentTimeMillis();
    private long worker = 0L;
    private long sequence = 0L;

    private long getWorker(ZooKeeper zk, String path, Set<Long> workers) throws KeeperException, InterruptedException {
        String sequence = zk.create(path, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        long worker = Long.parseLong(sequence.substring(sequence.length() - 10)) & 0xFFFL;
        if (workers.contains(worker)) {
            return getWorker(zk, path, workers);
        }
        return worker;
    }

    @PostConstruct
    void init() throws IOException, KeeperException, InterruptedException, ParseException {
        ZooKeeper zk = new ZooKeeper(addr, 5000, event -> {
        });
        Stat stat = zk.exists(path, false);
        if(stat == null){
            zk.create(path, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        //
        byte[] data = zk.getData(path, false, null);
        Set<Long> workers = (data == null) ? new HashSet<>() : JSON.parse(new String(data), Set.class);
        worker = getWorker(zk, path, workers);
        workers.add(worker);
        zk.setData(path, JSON.json(workers).getBytes(), -1);
        zk.close();
        logger.info("Snowflake's worker={}", worker);
    }

    @PreDestroy
    void destroy() throws IOException, InterruptedException, KeeperException, ParseException {
        ZooKeeper zk = new ZooKeeper(addr, 5000, event -> {
        });
        byte[] data = zk.getData(path, false, null);
        Set<Long> workers = JSON.parse(new String(data), Set.class);
        workers.remove(worker);
        zk.setData(path, JSON.json(workers).getBytes(), -1);
        zk.close();
    }

    public synchronized long getId() {
        long now = System.currentTimeMillis();
        if (now > timestamp) {
            timestamp = now;
            sequence = 0L;
        } else if (now == timestamp) {
            sequence++;
            if (sequence > 0xFFFL) {
                while (System.currentTimeMillis() <= timestamp) {
                }
                timestamp = System.currentTimeMillis();
                sequence = 0L;
            }
        } else if (now < timestamp) {
            logger.warn("Clock is moving backwards. Rejecting requests till {}.", timestamp);
            throw new RuntimeException(String.format("Clock moved backwards. Refusing to create for %d milliseconds", timestamp - now));
        }
        return getId(timestamp, worker, sequence);
    }

    private long getId(long timestamp, long worker, long sequence) {
        // 1451577600000: 2016-01-01 00:00:00
        return (timestamp - 1451577600000L) << 22 | worker << 12 | sequence;
    }
}
